/*-------------------------------------------------------------------------
 *
 * dsm.c
 *	  manage dynamic shared memory segments
 *
 * 本文件提供了一系列服务，使得动态共享内存段的编程更加方便。
 * 与 dsm_impl.h 和 dsm_impl.c 提供的低级设施不同，使用此模块创建的映射和段
 * 将自动清理。当创建它们的资源所有者被清理时，映射将被删除，除非使用
 * dsm_pin_mapping()，在这种情况下，它们的生命周期为会话级。段将在没有剩余
 * 映射时删除，或者在任何情况下在进程关闭时删除。在硬性进程崩溃后，剩余的段
 * 将在下次进程启动时被删除（如果仍然存在）。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/ipc/dsm.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <fcntl.h>
#include <unistd.h>
#ifndef WIN32
#include <sys/mman.h>
#endif
#include <sys/stat.h>

#include "common/pg_prng.h"
#include "lib/ilist.h"
#include "miscadmin.h"
#include "port/pg_bitutils.h"
#include "storage/dsm.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/pg_shmem.h"
#include "utils/freepage.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/resowner_private.h"

#define PG_DYNSHMEM_CONTROL_MAGIC		0x9a503d32

#define PG_DYNSHMEM_FIXED_SLOTS			64
#define PG_DYNSHMEM_SLOTS_PER_BACKEND	5

#define INVALID_CONTROL_SLOT		((uint32) -1)

/* 后端本地跟踪分离回调。 */
typedef struct dsm_segment_detach_callback
{
	on_dsm_detach_callback function;
	Datum		arg;
	slist_node	node;
} dsm_segment_detach_callback;

/* 后端本地状态，适用于动态共享内存段。 */
struct dsm_segment
{
	dlist_node	node;			/* dsm_segment_list中的列表链接。 */
	ResourceOwner resowner;		
	dsm_handle	handle;			/* 段名称。 */
	uint32		control_slot;	/* 控制段中的槽。 */
	void	   *impl_private;	/* 实现特定的私有数据。 */
	void	   *mapped_address; /* 映射地址，如果未映射则为 NULL。 */
	Size		mapped_size;	/* 我们的映射大小。 */
	slist_head	on_detach;		/* 断开连接回调。 */
};

/* 动态共享内存段的共享内存状态。 */
typedef struct dsm_control_item
{
	dsm_handle	handle;
	uint32		refcnt;			/* 2+ = 活跃，1 = 衰退，0 = 消失 */
	size_t		first_page;
	size_t		npages;
	void	   *impl_private_pm_handle; /* 仅在 Windows 上需要 */
	bool		pinned;
} dsm_control_item;

/* 动态共享内存控制段的布局。 */
typedef struct dsm_control_header
{
	uint32		magic;
	uint32		nitems;
	uint32		maxitems;
	dsm_control_item item[FLEXIBLE_ARRAY_MEMBER];
} dsm_control_header;

static void fc_dsm_cleanup_for_mmap(void);
static void fc_dsm_postmaster_shutdown(int fc_code, Datum fc_arg);
static dsm_segment *fc_dsm_create_descriptor(void);
static bool fc_dsm_control_segment_sane(dsm_control_header *fc_control,
									 Size fc_mapped_size);
static uint64 fc_dsm_control_bytes_needed(uint32 fc_nitems);
static inline dsm_handle fc_make_main_region_dsm_handle(int fc_slot);
static inline bool fc_is_main_region_dsm_handle(dsm_handle fc_handle);

/* 这个后端是否已经初始化了动态共享内存系统？ */
static bool dsm_init_done = false;

/* 在主共享内存区域中预分配的 DSM 空间。 */
static void *dsm_main_space_begin = NULL;

/*
 * 这个后端使用的动态共享内存段列表。
 *
 * 在进程退出时，我们必须递减每个已附加段的引用计数；
 * 这个列表使得找到所有这样的段成为可能。
 *
 * 在主进程中，这个列表应该始终为空。我们可能
 * 允许主进程在开始启动子进程之前映射动态共享内存段，
 * 前提是每个进程在启动时调整段在控制段中的引用计数，
 * 但没有明显的必要性来处理这样的设施，这在 EXEC_BACKEND 的情况下也会很复杂。
 * 一旦主进程开始生成子进程，就会出现额外的问题：
 * 每个新的映射都需要更新控制段，
 * 这需要锁定，而主进程不得参与其中。
 */
static dlist_head dsm_segment_list = DLIST_STATIC_INIT(dsm_segment_list);

/*
 * 控制段信息。
 *
 * 与普通的共享内存段不同，控制段不进行引用计数；
 * 相反，它在主进程的整个生命周期内保持存在。
 * 为了简单起见，它也没有 dsm_segment 对象。
 */
static dsm_handle dsm_control_handle;
static dsm_control_header *dsm_control;
static Size dsm_control_mapped_size = 0;
static void *dsm_control_impl_private = NULL;

/*
 * 启动动态共享内存系统。
 *
 * 这在每个集群生命周期中只调用一次，在主进程
 * 启动时。
 */
void dsm_postmaster_startup(PGShmemHeader *fc_shim)
{
	void	   *fc_dsm_control_address = NULL;
	uint32		fc_maxitems;
	Size		fc_segsize;

	Assert(!IsUnderPostmaster);

	/*
	 * 如果我们使用 mmap 实现，清理任何剩余物。
	 * Windows 上不需要清理，而在 POSIX 和 System V 共享内存的启动中会更早地进行，
	 * 通过对 dsm_cleanup_using_control_segment 的直接调用。
	 */
	if (dynamic_shared_memory_type == DSM_IMPL_MMAP)
		fc_dsm_cleanup_for_mmap();

	/* 确定新控制段的大小。 */
	fc_maxitems = PG_DYNSHMEM_FIXED_SLOTS
		+ PG_DYNSHMEM_SLOTS_PER_BACKEND * MaxBackends;
	elog(DEBUG2, "dynamic shared memory system will support %u segments",
		 fc_maxitems);
	fc_segsize = fc_dsm_control_bytes_needed(fc_maxitems);

	/*
	 * 循环直到找到用于新控制段的未使用标识符。
	 * 我们有时使用 0 作为哨兵值，表示没有已知的控制段
	 * 存在，因此避免将该值用于真实的控制段。
	 */
	for (;;)
	{
		Assert(fc_dsm_control_address == NULL);
		Assert(dsm_control_mapped_size == 0);
		/* 仅使用偶数 */
		dsm_control_handle = pg_prng_uint32(&pg_global_prng_state) << 1;
		if (dsm_control_handle == DSM_HANDLE_INVALID)
			continue;
		if (dsm_impl_op(DSM_OP_CREATE, dsm_control_handle, fc_segsize,
						&dsm_control_impl_private, &fc_dsm_control_address,
						&dsm_control_mapped_size, ERROR))
			break;
	}
	dsm_control = fc_dsm_control_address;
	on_shmem_exit(fc_dsm_postmaster_shutdown, PointerGetDatum(fc_shim));
	elog(DEBUG2,
		 "created dynamic shared memory control segment %u (%zu bytes)",
		 dsm_control_handle, fc_segsize);
	fc_shim->dsm_control = dsm_control_handle;

	/* 初始化控制段。 */
	dsm_control->magic = PG_DYNSHMEM_CONTROL_MAGIC;
	dsm_control->nitems = 0;
	dsm_control->maxitems = fc_maxitems;
}

/*
 * 确定之前主进程调用的控制段是否仍然存在。
 * 如果存在，移除它所引用的动态共享内存段，然后移除控制段本身。
 */
void dsm_cleanup_using_control_segment(dsm_handle fc_old_control_handle)
{
	void	   *fc_mapped_address = NULL;
	void	   *fc_junk_mapped_address = NULL;
	void	   *fc_impl_private = NULL;
	void	   *fc_junk_impl_private = NULL;
	Size		fc_mapped_size = 0;
	Size		fc_junk_mapped_size = 0;
	uint32		fc_nitems;
	uint32		fc_i;
	dsm_control_header *fc_old_control;

	/*
	 * 尝试附加该段。如果这失败了，可能只是表示
	 * 操作系统已重启，该段不再存在，或者无关的进程使用了相同的 shm ID。
	 * 所以就安静地退出吧。
	 */
	if (!dsm_impl_op(DSM_OP_ATTACH, fc_old_control_handle, 0, &fc_impl_private,
					 &fc_mapped_address, &fc_mapped_size, DEBUG1))
		return;

	/*
	 * 我们已成功重新附加它，但内容可能不正常。如果
	 * 它们不正常，我们最终会忽略该段。
	 */
	fc_old_control = (dsm_control_header *) fc_mapped_address;
	if (!fc_dsm_control_segment_sane(fc_old_control, fc_mapped_size))
	{
		dsm_impl_op(DSM_OP_DETACH, fc_old_control_handle, 0, &fc_impl_private,
					&fc_mapped_address, &fc_mapped_size, LOG);
		return;
	}

	/*
	 * 好吧，控制段基本上看起来有效，所以我们可以使用它来获取
	 * 需要移除的段列表。
	 */
	fc_nitems = fc_old_control->nitems;
	for (fc_i = 0; fc_i < fc_nitems; ++fc_i)
	{
		dsm_handle	fc_handle;
		uint32		fc_refcnt;

		/* 如果引用计数为 0，则槽实际上未使用。 */
		fc_refcnt = fc_old_control->item[fc_i].refcnt;
		if (fc_refcnt == 0)
			continue;

		/* 如果它使用的是主共享内存区域，则无需操作。 */
		fc_handle = fc_old_control->item[fc_i].handle;
		if (fc_is_main_region_dsm_handle(fc_handle))
			continue;

		/* 记录调试信息。 */
		elog(DEBUG2, "cleaning up orphaned dynamic shared memory with ID %u (reference count %u)",
			 fc_handle, fc_refcnt);

		/* 销毁被引用的段。 */
		dsm_impl_op(DSM_OP_DESTROY, fc_handle, 0, &fc_junk_impl_private,
					&fc_junk_mapped_address, &fc_junk_mapped_size, LOG);
	}

	/* 还要销毁旧的控制段。 */
	elog(DEBUG2,
		 "cleaning up dynamic shared memory control segment with ID %u",
		 fc_old_control_handle);
	dsm_impl_op(DSM_OP_DESTROY, fc_old_control_handle, 0, &fc_impl_private,
				&fc_mapped_address, &fc_mapped_size, LOG);
}

/*
 * 当我们使用 mmap 共享内存实现时，“共享内存”
 * 段甚至可能设法在操作系统重启后生存下来。
 * 但没有任何保证可以确切知道什么会生存下来：一些段
 * 可能生存，而其他段可能不生存，并且某些段的内容可能过时。
 * 特别是，控制段可能过时，因此我们不能依赖它来确定
 * 要移除的内容。然而，既然我们知道哪个目录包含作为共享内存使用的文件，
 * 我们可以简单地扫描该目录并删除所有不应存在的文件。
 */
static void fc_dsm_cleanup_for_mmap(void)
{
	DIR		   *fc_dir;
	struct dirent *fc_dent;

	/* 扫描目录以寻找名称格式正确的内容。 */
	fc_dir = AllocateDir(PG_DYNSHMEM_DIR);

	while ((fc_dent = ReadDir(fc_dir, PG_DYNSHMEM_DIR)) != NULL)
	{
		if (strncmp(fc_dent->d_name, PG_DYNSHMEM_MMAP_FILE_PREFIX,
					strlen(PG_DYNSHMEM_MMAP_FILE_PREFIX)) == 0)
		{
			char		fc_buf[MAXPGPATH + sizeof(PG_DYNSHMEM_DIR)];

			snprintf(fc_buf, sizeof(fc_buf), PG_DYNSHMEM_DIR "/%s", fc_dent->d_name);

			elog(DEBUG2, "removing file \"%s\"", fc_buf);

			/* 我们找到了匹配的文件；所以移除它。 */
			if (unlink(fc_buf) != 0)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not remove file \"%s\": %m", fc_buf)));
		}
	}

	/* 清理完成。 */
	FreeDir(fc_dir);
}

/*
 * 在关机时，我们遍历控制段并移除所有剩余的动态共享内存段。我们避免在这里抛出错误；
 * 无论如何，主进程正在关机，这只是非关键的资源清理。
 */
static void fc_dsm_postmaster_shutdown(int fc_code, Datum fc_arg)
{
	uint32		fc_nitems;
	uint32		fc_i;
	void	   *fc_dsm_control_address;
	void	   *fc_junk_mapped_address = NULL;
	void	   *fc_junk_impl_private = NULL;
	Size		fc_junk_mapped_size = 0;
	PGShmemHeader *fc_shim = (PGShmemHeader *) DatumGetPointer(fc_arg);

	/*
	 * 如果某个其他后端不干净地退出，它可能在死亡的时候损坏了控制段。在这种情况下，我们会发出警告并忽略控制段的内容。
	 * 这可能会留下多余的共享内存段，但如果元数据丢失，我们对此无能为力。
	 */
	fc_nitems = dsm_control->nitems;
	if (!fc_dsm_control_segment_sane(dsm_control, dsm_control_mapped_size))
	{
		ereport(LOG,
				(errmsg("dynamic shared memory control segment is corrupt")));
		return;
	}

	/* 移除任何剩余的段。 */
	for (fc_i = 0; fc_i < fc_nitems; ++fc_i)
	{
		dsm_handle	fc_handle;

		/* 如果引用计数为 0，则槽实际上未使用。 */
		if (dsm_control->item[fc_i].refcnt == 0)
			continue;

		fc_handle = dsm_control->item[fc_i].handle;
		if (fc_is_main_region_dsm_handle(fc_handle))
			continue;

		/* 记录调试信息。 */
		elog(DEBUG2, "cleaning up orphaned dynamic shared memory with ID %u",
			 fc_handle);

		/* 销毁该段。 */
		dsm_impl_op(DSM_OP_DESTROY, fc_handle, 0, &fc_junk_impl_private,
					&fc_junk_mapped_address, &fc_junk_mapped_size, LOG);
	}

	/* 移除控制段本身。 */
	elog(DEBUG2,
		 "cleaning up dynamic shared memory control segment with ID %u",
		 dsm_control_handle);
	fc_dsm_control_address = dsm_control;
	dsm_impl_op(DSM_OP_DESTROY, dsm_control_handle, 0,
				&dsm_control_impl_private, &fc_dsm_control_address,
				&dsm_control_mapped_size, LOG);
	dsm_control = fc_dsm_control_address;
	fc_shim->dsm_control = 0;
}

/*
 * 为该后端准备动态共享内存的使用。在 EXEC_BACKEND 下，
 * 我们必须重新读取状态文件并映射控制段；在其他情况下，
 * 我们将继承主进程的映射和全局变量。
 */
static void fc_dsm_backend_startup(void)
{
#ifdef EXEC_BACKEND
	if (IsUnderPostmaster)
	{
		void	   *fc_control_address = NULL;

		/* 附加控制段。 */
		Assert(dsm_control_handle != 0);
		dsm_impl_op(DSM_OP_ATTACH, dsm_control_handle, 0,
					&dsm_control_impl_private, &fc_control_address,
					&dsm_control_mapped_size, ERROR);
		dsm_control = fc_control_address;
		/* 如果控制段看起来不正常，说明出现了严重问题。 */
		if (!fc_dsm_control_segment_sane(dsm_control, dsm_control_mapped_size))
		{
			dsm_impl_op(DSM_OP_DETACH, dsm_control_handle, 0,
						&dsm_control_impl_private, &fc_control_address,
						&dsm_control_mapped_size, WARNING);
			ereport(FATAL,
					(errcode(ERRCODE_INTERNAL_ERROR),
					 errmsg("dynamic shared memory control segment is not valid")));
		}
	}
#endif

	dsm_init_done = true;
}

#ifdef EXEC_BACKEND
/*
 * 在 EXEC_BACKEND 下运行时，当主共享内存段重新附加时，我们会在这里得到一个回调，
 * 以便我们可以记录从中检索到的控制句柄。
 */
void dsm_set_control_handle(dsm_handle fc_h)
{
	Assert(dsm_control_handle == 0 && fc_h != 0);
	dsm_control_handle = fc_h;
}
#endif

/*
 * 在主共享内存段中为 DSM 段保留一些空间。
 */
size_t dsm_estimate_size(void)
{
	return 1024 * 1024 * (size_t) min_dynamic_shared_memory;
}

/*
 * 在主共享内存段中初始化 DSM 段的空间。
 */
void dsm_shmem_init(void)
{
	size_t		fc_size = dsm_estimate_size();
	bool		fc_found;

	if (fc_size == 0)
		return;

	dsm_main_space_begin = ShmemInitStruct("Preallocated DSM", fc_size, &fc_found);
	if (!fc_found)
	{
		FreePageManager *fc_fpm = (FreePageManager *) dsm_main_space_begin;
		size_t		fc_first_page = 0;
		size_t		fc_pages;

		/* 为 FreePageManager 保留空间。 */
		while (fc_first_page * FPM_PAGE_SIZE < sizeof(FreePageManager))
			++fc_first_page;

		/* 初始化它并将所有剩余空间提供给它。 */
		FreePageManagerInitialize(fc_fpm, dsm_main_space_begin);
		fc_pages = (fc_size / FPM_PAGE_SIZE) - fc_first_page;
		FreePageManagerPut(fc_fpm, fc_first_page, fc_pages);
	}
}

/*
 * 创建一个新的动态共享内存段。
 *
 * 如果 CurrentResourceOwner 不是 NULL, 新段就与其相关联，
 * 并且必须在资源所有者释放之前分离，否则将记录警告。
 * 如果 CurrentResourceOwner 为 NULL, 该段将保持附加状态，
 * 直到显式分离或会话结束。
 * 使用 NULL 的 CurrentResourceOwner 创建相当于使用非 NULL 的 CurrentResourceOwner
 * 创建后再调用 dsm_pin_mapping。
 */
dsm_segment * dsm_create(Size fc_size, int fc_flags)
{
	dsm_segment *fc_seg;
	uint32		fc_i;
	uint32		fc_nitems;
	size_t		fc_npages = 0;
	size_t		fc_first_page = 0;
	FreePageManager *fc_dsm_main_space_fpm = dsm_main_space_begin;
	bool		fc_using_main_dsm_region = false;

	/*
	 * 在主进程中不安全。在单用户模式下允许使用 dsm 似乎毫无意义，
	 * 但否则某些子系统将需要专用的单用户模式代码路径。
	 */
	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);

	if (!dsm_init_done)
		fc_dsm_backend_startup();

	/* 创建一个新的段描述符。 */
	fc_seg = fc_dsm_create_descriptor();

	/*
	 * 在尝试从主共享内存区域分配时，如果已配置，请锁定控制段。
	 */
	if (fc_dsm_main_space_fpm)
	{
		fc_npages = fc_size / FPM_PAGE_SIZE;
		if (fc_size % FPM_PAGE_SIZE > 0)
			++fc_npages;

		LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
		if (FreePageManagerGet(fc_dsm_main_space_fpm, fc_npages, &fc_first_page))
		{
			/* 我们可以从主共享内存段中划出一块空间。 */
			fc_seg->mapped_address = (char *) dsm_main_space_begin +
				fc_first_page * FPM_PAGE_SIZE;
			fc_seg->mapped_size = fc_npages * FPM_PAGE_SIZE;
			fc_using_main_dsm_region = true;
			/* 我们将选择一个句柄。 */
		}
	}

	if (!fc_using_main_dsm_region)
	{
		/*
		 * 我们需要创建一个新的内存段。循环直到找到一个
		 * 未使用的段标识符。
		 */
		if (fc_dsm_main_space_fpm)
			LWLockRelease(DynamicSharedMemoryControlLock);
		for (;;)
		{
			Assert(fc_seg->mapped_address == NULL && fc_seg->mapped_size == 0);
			/* 仅使用偶数 */
			fc_seg->handle = pg_prng_uint32(&pg_global_prng_state) << 1;
			if (fc_seg->handle == DSM_HANDLE_INVALID)	/* 保留哨兵 */
				continue;
			if (dsm_impl_op(DSM_OP_CREATE, fc_seg->handle, fc_size, &fc_seg->impl_private,
							&fc_seg->mapped_address, &fc_seg->mapped_size, ERROR))
				break;
		}
		LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
	}

	/* 在控制段中搜索未使用的槽。 */
	fc_nitems = dsm_control->nitems;
	for (fc_i = 0; fc_i < fc_nitems; ++fc_i)
	{
		if (dsm_control->item[fc_i].refcnt == 0)
		{
			if (fc_using_main_dsm_region)
			{
				fc_seg->handle = fc_make_main_region_dsm_handle(fc_i);
				dsm_control->item[fc_i].first_page = fc_first_page;
				dsm_control->item[fc_i].npages = fc_npages;
			}
			else
				Assert(!fc_is_main_region_dsm_handle(fc_seg->handle));
			dsm_control->item[fc_i].handle = fc_seg->handle;
			/* 引用计数为 1 触发销毁，因此从 2 开始 */
			dsm_control->item[fc_i].refcnt = 2;
			dsm_control->item[fc_i].impl_private_pm_handle = NULL;
			dsm_control->item[fc_i].pinned = false;
			fc_seg->control_slot = fc_i;
			LWLockRelease(DynamicSharedMemoryControlLock);
			return fc_seg;
		}
	}

	/* 验证我们是否可以支持额外的映射。 */
	if (fc_nitems >= dsm_control->maxitems)
	{
		if (fc_using_main_dsm_region)
			FreePageManagerPut(fc_dsm_main_space_fpm, fc_first_page, fc_npages);
		LWLockRelease(DynamicSharedMemoryControlLock);
		if (!fc_using_main_dsm_region)
			dsm_impl_op(DSM_OP_DESTROY, fc_seg->handle, 0, &fc_seg->impl_private,
						&fc_seg->mapped_address, &fc_seg->mapped_size, WARNING);
		if (fc_seg->resowner != NULL)
			ResourceOwnerForgetDSM(fc_seg->resowner, fc_seg);
		dlist_delete(&fc_seg->node);
		pfree(fc_seg);

		if ((fc_flags & DSM_CREATE_NULL_IF_MAXSEGMENTS) != 0)
			return NULL;
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("too many dynamic shared memory segments")));
	}

	/* 将句柄放入新的数组槽中。 */
	if (fc_using_main_dsm_region)
	{
		fc_seg->handle = fc_make_main_region_dsm_handle(fc_nitems);
		dsm_control->item[fc_i].first_page = fc_first_page;
		dsm_control->item[fc_i].npages = fc_npages;
	}
	dsm_control->item[fc_nitems].handle = fc_seg->handle;
	/* 引用计数为 1 触发销毁，因此从 2 开始 */
	dsm_control->item[fc_nitems].refcnt = 2;
	dsm_control->item[fc_nitems].impl_private_pm_handle = NULL;
	dsm_control->item[fc_nitems].pinned = false;
	fc_seg->control_slot = fc_nitems;
	dsm_control->nitems++;
	LWLockRelease(DynamicSharedMemoryControlLock);

	return fc_seg;
}

/*
 * 附加一个动态共享内存段。
 *
 * 有关如何使用此功能的解释，请参见 dsm_segment_handle() 的注释。
 *
 * 如果段在系统中未知，则此函数将返回 NULL。
 * 如果我们被要求附加段，但随后其他所有人都分离它（导致其被销毁），
 * 在我们附加它之前，这种情况可能会发生。
 *
 * 如果有非 NULL 的 CurrentResourceOwner，则附加的段与其相关联，
 * 必须在资源所有者释放之前分离，否则将记录警告。
 * 否则该段将保持附加状态，直到显式分离或会话结束。
 * 请参见 dsm_create() 顶部的注释。
 */
dsm_segment * dsm_attach(dsm_handle fc_h)
{
	dsm_segment *fc_seg;
	dlist_iter	fc_iter;
	uint32		fc_i;
	uint32		fc_nitems;

	/* 在主进程中不安全（在独立后端中毫无意义）。 */
	Assert(IsUnderPostmaster);

	if (!dsm_init_done)
		fc_dsm_backend_startup();

	/*
	 * 由于这只是一个调试交叉检查，我们可以完全省略它，
	 * 或者仅在启用断言的构建中包含它。但由于
	 * 附加段的列表通常应该非常短，因此我们现在决定始终包含它。
	 *
	 * 如果您遇到此错误，您可能想在调用 dsm_attach() 创建新映射之前，
	 * 尝试通过 dsm_find_mapping() 找到现有映射。
	 */
	dlist_foreach(fc_iter, &dsm_segment_list)
	{
		fc_seg = dlist_container(dsm_segment, node, fc_iter.cur);
		if (fc_seg->handle == fc_h)
			elog(ERROR, "can't attach the same segment more than once");
	}

	/* 创建一个新的段描述符。 */
	fc_seg = fc_dsm_create_descriptor();
	fc_seg->handle = fc_h;

	/* 增加此段在共享内存中的引用计数。 */
	LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
	fc_nitems = dsm_control->nitems;
	for (fc_i = 0; fc_i < fc_nitems; ++fc_i)
	{
		/*
		 * 如果引用计数为 0，则该插槽实际上未被使用。
		 * 如果引用计数为 1，则插槽仍在使用中，但该段正在
		 * 逐步消失；即使句柄匹配，另一个插槽也可能已经
		 * 由于巧合而开始使用相同的句柄值，因此我们必须继续搜索。
		 */
		if (dsm_control->item[fc_i].refcnt <= 1)
			continue;

		/* 如果句柄不匹配，则不是我们想要的插槽。 */
		if (dsm_control->item[fc_i].handle != fc_seg->handle)
			continue;

		/* 否则我们找到了匹配项。 */
		dsm_control->item[fc_i].refcnt++;
		fc_seg->control_slot = fc_i;
		if (fc_is_main_region_dsm_handle(fc_seg->handle))
		{
			fc_seg->mapped_address = (char *) dsm_main_space_begin +
				dsm_control->item[fc_i].first_page * FPM_PAGE_SIZE;
			fc_seg->mapped_size = dsm_control->item[fc_i].npages * FPM_PAGE_SIZE;
		}
		break;
	}
	LWLockRelease(DynamicSharedMemoryControlLock);

	/*
	 * 如果我们没有在控制段中找到所需的句柄，
	 * 这可能意味着所有其他映射它的用户，包括原始创建者，
	 * 在我们达到这一点之前就已经退出了。由调用者决定
	 * 如何处理这个问题。
	 */
	if (fc_seg->control_slot == INVALID_CONTROL_SLOT)
	{
		dsm_detach(fc_seg);
		return NULL;
	}

	/* 在这里我们实际尝试映射该段。 */
	if (!fc_is_main_region_dsm_handle(fc_seg->handle))
		dsm_impl_op(DSM_OP_ATTACH, fc_seg->handle, 0, &fc_seg->impl_private,
					&fc_seg->mapped_address, &fc_seg->mapped_size, ERROR);

	return fc_seg;
}

/*
 * 在后台关闭时，分离任何仍然附加的段。
 * （这与 dsm_detach_all 类似，只是没有必要在退出之前
 * 取消映射控制段，所以我们就不这样做。）
 */
void dsm_backend_shutdown(void)
{
	while (!dlist_is_empty(&dsm_segment_list))
	{
		dsm_segment *fc_seg;

		fc_seg = dlist_head_element(dsm_segment, node, &dsm_segment_list);
		dsm_detach(fc_seg);
	}
}

/*
 * 分离所有共享内存段，包括控制段。此操作
 * 应该与 PGSharedMemoryDetach 一起在可能继承映射但
 * 不打算链接到动态共享内存的进程中调用。
 */
void dsm_detach_all(void)
{
	void	   *fc_control_address = dsm_control;

	while (!dlist_is_empty(&dsm_segment_list))
	{
		dsm_segment *fc_seg;

		fc_seg = dlist_head_element(dsm_segment, node, &dsm_segment_list);
		dsm_detach(fc_seg);
	}

	if (fc_control_address != NULL)
		dsm_impl_op(DSM_OP_DETACH, dsm_control_handle, 0,
					&dsm_control_impl_private, &fc_control_address,
					&dsm_control_mapped_size, ERROR);
}

/*
 * 从共享内存段中分离，如果我们移除了最后一个引用，则销毁该段。
 *
 * 此函数不应失败。它通常在中止事务时调用，进一步的错误
 * 将没有任何意义。如果我们无法取消映射或销毁该段，这并不
 * 是一场彻底的灾难；这意味着资源泄漏，但并不一定排除进一步的操作。
 */
void dsm_detach(dsm_segment *fc_seg)
{
	/*
	 * 调用注册的回调函数。以防其中一个回调函数
	 * 抛出进一步的错误并将我们带回这里，在调用它之前
	 * 弹出回调，以避免无限错误递归。在运行个别回调时不要
	 * 允许中断，以避免在被语句超时或类似情况中断时留下清理工作未完成。
	 */
	HOLD_INTERRUPTS();
	while (!slist_is_empty(&fc_seg->on_detach))
	{
		slist_node *fc_node;
		dsm_segment_detach_callback *fc_cb;
		on_dsm_detach_callback fc_function;
		Datum		fc_arg;

		fc_node = slist_pop_head_node(&fc_seg->on_detach);
		fc_cb = slist_container(dsm_segment_detach_callback, node, fc_node);
		fc_function = fc_cb->function;
		fc_arg = fc_cb->arg;
		pfree(fc_cb);

		fc_function(fc_seg, fc_arg);
	}
	RESUME_INTERRUPTS();

	/*
	 * 尝试移除映射（如果存在）。通常会有映射，但如果我们
	 * 在创建或附加操作中半途而废，则可能没有。
	 * 我们在减少引用计数之前移除映射，以便
	 * 看到零引用计数的进程可以确认没有剩余映射存在。
	 * 即使这失败，我们也假装它成功了，因为重试很可能
	 * 以相同方式失败。
	 */
	if (fc_seg->mapped_address != NULL)
	{
		if (!fc_is_main_region_dsm_handle(fc_seg->handle))
			dsm_impl_op(DSM_OP_DETACH, fc_seg->handle, 0, &fc_seg->impl_private,
						&fc_seg->mapped_address, &fc_seg->mapped_size, WARNING);
		fc_seg->impl_private = NULL;
		fc_seg->mapped_address = NULL;
		fc_seg->mapped_size = 0;
	}

	/* 减少引用计数，如果我们之前增加了它。 */
	if (fc_seg->control_slot != INVALID_CONTROL_SLOT)
	{
		uint32		fc_refcnt;
		uint32		fc_control_slot = fc_seg->control_slot;

		LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
		Assert(dsm_control->item[fc_control_slot].handle == fc_seg->handle);
		Assert(dsm_control->item[fc_control_slot].refcnt > 1);
		fc_refcnt = --dsm_control->item[fc_control_slot].refcnt;
		fc_seg->control_slot = INVALID_CONTROL_SLOT;
		LWLockRelease(DynamicSharedMemoryControlLock);

		/* 如果新的引用计数为 1，尝试销毁该段。 */
		if (fc_refcnt == 1)
		{
			/* 被固定的段永远不应该达到 1。 */
			Assert(!dsm_control->item[fc_control_slot].pinned);

			/*
			 * 如果我们在这里未能销毁该段，或者在完成销毁之前被杀掉，
			 * 引用计数将保持为 1，这将意味着没有其他人可以附加到该段。
			 * 在主进程关闭时，或者在经硬杀死后启动新的主进程时，
			 * 将会再次尝试移除该段。
			 *
			 * 我们在这里担心的主要情况是，在完成移除该段之前
			 * 被信号杀掉。在这种情况下，确保该段仍然被移除是重要的。
			 * 如果我们因其他原因实际上未能移除该段，主进程的运气
			 * 可能也不会比我们更好。不过，我们对此没有太多可以做的。
			 */
			if (fc_is_main_region_dsm_handle(fc_seg->handle) ||
				dsm_impl_op(DSM_OP_DESTROY, fc_seg->handle, 0, &fc_seg->impl_private,
							&fc_seg->mapped_address, &fc_seg->mapped_size, WARNING))
			{
				LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
				if (fc_is_main_region_dsm_handle(fc_seg->handle))
					FreePageManagerPut((FreePageManager *) dsm_main_space_begin,
									   dsm_control->item[fc_control_slot].first_page,
									   dsm_control->item[fc_control_slot].npages);
				Assert(dsm_control->item[fc_control_slot].handle == fc_seg->handle);
				Assert(dsm_control->item[fc_control_slot].refcnt == 1);
				dsm_control->item[fc_control_slot].refcnt = 0;
				LWLockRelease(DynamicSharedMemoryControlLock);
			}
		}
	}

	/* 清理我们剩余的后端私有数据结构。 */
	if (fc_seg->resowner != NULL)
		ResourceOwnerForgetDSM(fc_seg->resowner, fc_seg);
	dlist_delete(&fc_seg->node);
	pfree(fc_seg);
}

/*
 * 在会话结束时保持动态共享内存映射。
 *
 * 默认情况下，映射由当前资源所有者拥有，这通常意味着它们仅在
 * 当前查询的持续时间内停留。
 */
void dsm_pin_mapping(dsm_segment *fc_seg)
{
	if (fc_seg->resowner != NULL)
	{
		ResourceOwnerForgetDSM(fc_seg->resowner, fc_seg);
		fc_seg->resowner = NULL;
	}
}

/*
 * 安排在清理时移除动态共享内存映射。
 *
 * dsm_pin_mapping() 可用于在整个进程的生命周期内保留映射；
 * 此函数会逆转该决定，使该段由当前资源所有者拥有。这在执行某些
 * 操作之前可能很有用，因为这将使该段在此后不能再被此后端使用。
 */
void dsm_unpin_mapping(dsm_segment *fc_seg)
{
	Assert(fc_seg->resowner == NULL);
	ResourceOwnerEnlargeDSMs(CurrentResourceOwner);
	fc_seg->resowner = CurrentResourceOwner;
	ResourceOwnerRememberDSM(fc_seg->resowner, fc_seg);
}


/*
 * 保持动态共享内存段，直到主进程关闭，或者直到调用
 * dsm_unpin_segment。
 *
 * 除非在调用之间明确调用 dsm_unpin_segment 取消固定，
 * 否则此功能不应对每个段调用超过一次。
 *
 * 注意，此函数不安排当前进程无限期保持段映射；
 * 如果需要这种行为，应使用 dsm_pin_mapping() 从每个需要
 * 保持映射的进程调用。
 */
void dsm_pin_segment(dsm_segment *fc_seg)
{
	void	   *fc_handle = NULL;

	/*
	 * 在共享内存中增加此段的引用计数。这将确保即使没有
	 * 任何会话附加到此段，它也将保持直到主进程关闭或
	 * 显式调用取消固定。
	 */
	LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
	if (dsm_control->item[fc_seg->control_slot].pinned)
		elog(ERROR, "cannot pin a segment that is already pinned");
	if (!fc_is_main_region_dsm_handle(fc_seg->handle))
		dsm_impl_pin_segment(fc_seg->handle, fc_seg->impl_private, &fc_handle);
	dsm_control->item[fc_seg->control_slot].pinned = true;
	dsm_control->item[fc_seg->control_slot].refcnt++;
	dsm_control->item[fc_seg->control_slot].impl_private_pm_handle = fc_handle;
	LWLockRelease(DynamicSharedMemoryControlLock);
}

/*
 * 取消固定之前已用 dsm_pin_segment 固定的动态共享内存段。
 * 除非之前为该段调用过 dsm_pin_segment，否则不应调用此功能。
 *
 * 参数是 dsm_handle 而不是 dsm_segment，以防您想取消固定一个
 * 您尚未附加的段。这在例如一个共享内存段的引用存储在另一个
 * 共享内存段中时非常有用。您可能希望在销毁引用段之前
 * 取消固定被引用段。
 */
void dsm_unpin_segment(dsm_handle fc_handle)
{
	uint32		fc_control_slot = INVALID_CONTROL_SLOT;
	bool		fc_destroy = false;
	uint32		fc_i;

	/* 查找给定句柄的控制槽。 */
	LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
	for (fc_i = 0; fc_i < dsm_control->nitems; ++fc_i)
	{
		/* 跳过未使用的槽和正在同时消失的段。 */
		if (dsm_control->item[fc_i].refcnt <= 1)
			continue;

		/* 如果我们找到了句柄，就可以停止搜索。 */
		if (dsm_control->item[fc_i].handle == fc_handle)
		{
			fc_control_slot = fc_i;
			break;
		}
	}

	/*
	 * 我们肯定会找到槽，并且它不应该已经处于消失的过程中，
	 * 因为此函数应仅在固定的段上调用。
	 */
	if (fc_control_slot == INVALID_CONTROL_SLOT)
		elog(ERROR, "cannot unpin unknown segment handle");
	if (!dsm_control->item[fc_control_slot].pinned)
		elog(ERROR, "cannot unpin a segment that is not pinned");
	Assert(dsm_control->item[fc_control_slot].refcnt > 1);

	/*
	 * 允许实现特定的代码运行。我们必须在释放锁之前
	 * 执行此操作，因为 impl_private_pm_handle 可能会被
	 * dsm_impl_unpin_segment 修改。
	 */
	if (!fc_is_main_region_dsm_handle(fc_handle))
		dsm_impl_unpin_segment(fc_handle,
							   &dsm_control->item[fc_control_slot].impl_private_pm_handle);

	/* 注意 1 表示没有引用（0 表示未使用的槽）。 */
	if (--dsm_control->item[fc_control_slot].refcnt == 1)
		fc_destroy = true;
	dsm_control->item[fc_control_slot].pinned = false;

	/* 现在我们可以释放锁。 */
	LWLockRelease(DynamicSharedMemoryControlLock);

	/* 如果这是最后一个引用，则清理资源。 */
	if (fc_destroy)
	{
		void	   *fc_junk_impl_private = NULL;
		void	   *fc_junk_mapped_address = NULL;
		Size		fc_junk_mapped_size = 0;

		/*
		 * 在这种情况下，错误处理的工作原理的解释，请参见
		 * dsm_detach 中的注释。请注意，如果我们达到这个点，当前进程
		 * 肯定没有映射该段，因为如果有，引用计数在释放由固定保留的
		 * 引用计数后仍然会大于 1。由于这个句柄不能有 dsm_segment，
		 * 因此在这里将映射大小、映射地址和私有数据作为 NULL 传递是
		 * 可以的。
		 */
		if (fc_is_main_region_dsm_handle(fc_handle) ||
			dsm_impl_op(DSM_OP_DESTROY, fc_handle, 0, &fc_junk_impl_private,
						&fc_junk_mapped_address, &fc_junk_mapped_size, WARNING))
		{
			LWLockAcquire(DynamicSharedMemoryControlLock, LW_EXCLUSIVE);
			if (fc_is_main_region_dsm_handle(fc_handle))
				FreePageManagerPut((FreePageManager *) dsm_main_space_begin,
								   dsm_control->item[fc_control_slot].first_page,
								   dsm_control->item[fc_control_slot].npages);
			Assert(dsm_control->item[fc_control_slot].handle == fc_handle);
			Assert(dsm_control->item[fc_control_slot].refcnt == 1);
			dsm_control->item[fc_control_slot].refcnt = 0;
			LWLockRelease(DynamicSharedMemoryControlLock);
		}
	}
}

/*
 * 查找现有的共享内存段的映射（如果存在）。
 */
dsm_segment * dsm_find_mapping(dsm_handle fc_h)
{
	dlist_iter	fc_iter;
	dsm_segment *fc_seg;

	dlist_foreach(fc_iter, &dsm_segment_list)
	{
		fc_seg = dlist_container(dsm_segment, node, fc_iter.cur);
		if (fc_seg->handle == fc_h)
			return fc_seg;
	}

	return NULL;
}

/*
 * 获取动态共享内存段映射的地址。
 */
void * dsm_segment_address(dsm_segment *fc_seg)
{
	Assert(fc_seg->mapped_address != NULL);
	return fc_seg->mapped_address;
}

/*
 * 获取映射的大小。
 */
Size dsm_segment_map_length(dsm_segment *fc_seg)
{
	Assert(fc_seg->mapped_address != NULL);
	return fc_seg->mapped_size;
}

/*
 * 获取映射的句柄。
 *
 * 为了在两个后端之间通过动态共享内存建立通信，其中一个
 * 应该首先调用 dsm_create() 来建立新的共享内存映射。
 * 然后，该进程应调用 dsm_segment_handle() 来获取映射的句柄，并通过
 * 一些方式（例如，bgw_main_arg，或通过主共享内存段）将该句柄传递
 * 给协调的后端。接收方一旦拥有句柄，应该调用 dsm_attach()。
 */
dsm_handle
dsm_segment_handle(dsm_segment *fc_seg)
{
	return fc_seg->handle;
}

/*
 * 注册动态共享内存段的解绑回调。
 */
void on_dsm_detach(dsm_segment *fc_seg, on_dsm_detach_callback fc_function, Datum fc_arg)
{
	dsm_segment_detach_callback *fc_cb;

	fc_cb = MemoryContextAlloc(TopMemoryContext,
							sizeof(dsm_segment_detach_callback));
	fc_cb->function = fc_function;
	fc_cb->arg = fc_arg;
	slist_push_head(&fc_seg->on_detach, &fc_cb->node);
}

/*
 * 注销动态共享内存段的解绑回调。
 */
void cancel_on_dsm_detach(dsm_segment *fc_seg, on_dsm_detach_callback fc_function,
					 Datum fc_arg)
{
	slist_mutable_iter fc_iter;

	slist_foreach_modify(fc_iter, &fc_seg->on_detach)
	{
		dsm_segment_detach_callback *fc_cb;

		fc_cb = slist_container(dsm_segment_detach_callback, node, fc_iter.cur);
		if (fc_cb->function == fc_function && fc_cb->arg == fc_arg)
		{
			slist_delete_current(&fc_iter);
			pfree(fc_cb);
			break;
		}
	}
}

/*
 * 丢弃所有注册的解绑回调而不执行它们。
 */
void reset_on_dsm_detach(void)
{
	dlist_iter	fc_iter;

	dlist_foreach(fc_iter, &dsm_segment_list)
	{
		dsm_segment *fc_seg = dlist_container(dsm_segment, node, fc_iter.cur);

		/* 一个一个地丢弃显式的解绑操作。 */
		while (!slist_is_empty(&fc_seg->on_detach))
		{
			slist_node *fc_node;
			dsm_segment_detach_callback *fc_cb;

			fc_node = slist_pop_head_node(&fc_seg->on_detach);
			fc_cb = slist_container(dsm_segment_detach_callback, node, fc_node);
			pfree(fc_cb);
		}

		/*
		 * 递减引用计数是一种隐式的解绑操作；确保我们也不这样做。
		 */
		fc_seg->control_slot = INVALID_CONTROL_SLOT;
	}
}

/*
 * 创建段描述符。
 */
static dsm_segment *
fc_dsm_create_descriptor(void)
{
	dsm_segment *fc_seg;

	if (CurrentResourceOwner)
		ResourceOwnerEnlargeDSMs(CurrentResourceOwner);

	fc_seg = MemoryContextAlloc(TopMemoryContext, sizeof(dsm_segment));
	dlist_push_head(&dsm_segment_list, &fc_seg->node);

	/* seg->handle 必须由调用者初始化 */
	fc_seg->control_slot = INVALID_CONTROL_SLOT;
	fc_seg->impl_private = NULL;
	fc_seg->mapped_address = NULL;
	fc_seg->mapped_size = 0;

	fc_seg->resowner = CurrentResourceOwner;
	if (CurrentResourceOwner)
		ResourceOwnerRememberDSM(CurrentResourceOwner, fc_seg);

	slist_init(&fc_seg->on_detach);

	return fc_seg;
}

/*
 * 对控制段进行合理性检查。
 *
 * 这里的目标不是检测控制段中可能出现的所有问题；
 * 这没有足够的信息。相反，目标是确保某人在遍历段中的
 * 项目时不会越界映射的末尾而崩溃。我们还检查魔数，因为
 * 如果那被搞乱，这可能根本就不是我们的段之一。
 */
static bool fc_dsm_control_segment_sane(dsm_control_header *fc_control, Size fc_mapped_size)
{
	if (fc_mapped_size < offsetof(dsm_control_header, item))
		return false;			/* 映射的大小太短，无法读取头部。 */
	if (fc_control->magic != PG_DYNSHMEM_CONTROL_MAGIC)
		return false;			/* 魔数不匹配。 */
	if (fc_dsm_control_bytes_needed(fc_control->maxitems) > fc_mapped_size)
		return false;			/* 最大项数不能适配映射。 */
	if (fc_control->nitems > fc_control->maxitems)
		return false;			/* 溢出。 */
	return true;
}

/*
 * 计算存储给定数量项所需的控制段字节数。
 */
static uint64 fc_dsm_control_bytes_needed(uint32 fc_nitems)
{
	return offsetof(dsm_control_header, item)
		+ sizeof(dsm_control_item) * (uint64) fc_nitems;
}

static inline dsm_handle
fc_make_main_region_dsm_handle(int fc_slot)
{
	dsm_handle	fc_handle;

	/*
	 * 我们需要创建一个与 dsm_impl_op() 创建的任何现有
	 * 额外段不冲突的句柄，所以我们将其设为奇数。它也不能与
	 * 其他主区域伪段冲突，因此我们将在某些位中包括槽号。
	 * 我们还希望努力避免将新创建和最近销毁的句柄混淆，因此
	 * 我们将其余位设置为随机。
	 */
	fc_handle = 1;
	fc_handle |= fc_slot << 1;
	fc_handle |= pg_prng_uint32(&pg_global_prng_state) << (pg_leftmost_one_pos32(dsm_control->maxitems) + 1);
	return fc_handle;
}

static inline bool fc_is_main_region_dsm_handle(dsm_handle fc_handle)
{
	return fc_handle & 1;
}
